﻿using System;
using System.Collections.Generic;
using System.Linq;
using LinqExpressions = System.Linq.Expressions;
using System.Reflection;
using Xant.Querier.Core;

namespace Xant.Querier.Compilers
{

    public class LambdaExpressionCompiler<T> : QueryCompiler<LinqExpressions.Expression<Func<T, bool>>>
    {
        /// <summary>
        /// 记录已经分配使用的Linq参数名，避免重名
        /// </summary>
        private readonly List<string> assignedParameterName = new List<string>();

        LinqExpressions.ParameterExpression parameterExpression;

        public LambdaExpressionCompiler(Query query) : base(query)
        {
        }

        protected virtual LinqExpressions.Expression PretreatmentCriteria(Criteria criteria)
        {
            string path = null;
            if (criteria.Factor is Field)
            {
                path = (criteria.Factor as Field).Path;
            }
            else if (criteria.Factor is Formular)
            {
                Formular formular = criteria.Factor as Formular;
                if (formular.HasFieldInside(true))
                    path = formular.AllFields()[0].Path;
            }
            //如果字段路径包含多级(如Supplier.Code)，则生成Exists条件语句
            if (path != null && path.Contains('.'))
            {
                //var config = EntityConfigurationManager.FindEntityConfiguration(this.Query.SourceEntityType.Name);
                var propertyQueue = new Queue<string>(path.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries));
                return GetExpression(criteria, parameterExpression, propertyQueue);
            }
            else
            {
                return GetExpression(criteria);
            }
        }

        private LinqExpressions.Expression GetExpression(Criteria criteria, LinqExpressions.Expression ownerExpression, Queue<string> propertyQueue)
        {
            var propertyName = propertyQueue.Dequeue();
            var leftExpression = LinqExpressions.Expression.PropertyOrField(ownerExpression, propertyName);
            if (!propertyQueue.Any()) //如果是最后一个元素
            {
                var expr = GetExpression(leftExpression, criteria.Operator, criteria.Value);
                return expr;
            }

            var propertyInfo = ownerExpression.Type.GetProperty(propertyName);
            if (propertyInfo == null)
                throw new ApplicationException(string.Format("在类型{0}中未发现属性{1}", ownerExpression.Type.FullName, propertyName));
            var interfaceType = propertyInfo.PropertyType.GetInterface("IEnumerable`1");
            if (interfaceType != null)
            {
                var actualType = interfaceType.GetGenericArguments().FirstOrDefault();
                //获取Enumerable类泛型版本的Any()方法 //不再需要
                //var methodAny = typeof(Enumerable).GetMethods().Where(p => p.Name == "Any" && p.GetParameters().Length==2).First();
                var param = LinqExpressions.Expression.Parameter(actualType, AssignLinqParameterName(propertyName.Remove(1).ToLower()));
                var relatExpression = GetExpression(criteria, param, propertyQueue);
                var predicate = LinqExpressions.Expression.Lambda(relatExpression, param);
                //此处非常重要，花了很长时间才完成对Any方法的泛型调用
                var expr = LinqExpressions.Expression.Call(typeof(Enumerable), "Any",
                    new Type[] { actualType }, leftExpression, predicate);
                return expr;
            }
            else
            {
                var relatExpression = GetExpression(criteria, leftExpression, propertyQueue);
                return relatExpression;
            }
        }

        protected virtual LinqExpressions.Expression GetExpression(Criteria criteria)
        {
            var leftExpression = GetFactorExpression(criteria.Factor);
            return GetExpression(leftExpression, criteria.Operator, criteria.Value);
        }

        protected virtual  LinqExpressions.Expression GetExpression(LinqExpressions.Expression leftExpression, RelationalOperator operatr, Factor value)
        {
            var rightExpression = GetFactorExpression(value, leftExpression.Type);
            if (leftExpression.Type == typeof(String))
            {
                var methodInfo = typeof(string).GetMethod(operatr.ToString(), new[] { typeof(string) });
                return LinqExpressions.Expression.Call(leftExpression, methodInfo, rightExpression);
            }
            else
            {
                Func<LinqExpressions.Expression, LinqExpressions.Expression, LinqExpressions.BinaryExpression>
                    relationalOperationExpression = GetRelationalOperationExpression(operatr);
                return GetRelationalExpression(leftExpression, rightExpression, relationalOperationExpression);
            }
        }

        protected virtual LinqExpressions.Expression GetFactorExpression(Factor factor, Type change2Type = null)
        {
            if (factor is Constant) //常量
            {
                return GetExpression(factor as Constant, change2Type);
            }
            else if (factor is Macro) //宏
            {
                return GetExpression(factor as Macro, change2Type);
            }
            else if (factor is Field) //字段
            {
                return GetExpression(factor as Field);
            }
            else if (factor is Formular) //算式
            {
                return GetExpression(factor as Formular);
            }
            else
            {
                throw new Exception("不支持的因子类型：" + factor.GetType().FullName);
            }
        }

        private LinqExpressions.Expression GetExpression(Formular formular)
        {
            var factorStack = new Stack<Factor>();
            var outputStack = new Stack<LinqExpressions.Expression>();
            var visitor = new FactorVisitor(formular);
            visitor.Visit(factorStack);
            while (factorStack.Count > 0)
            {
                var item = factorStack.Pop();
                if (item is Formular)
                {
                    //    throw new NotImplementedException();
                    formular = item as Formular;
                    var left = outputStack.Pop();
                    var right = outputStack.Pop().ConvertTo(left.Type);
                    MethodInfo method=null;
                    LinqExpressions.Expression expression=null;
                    switch (formular.Operator)
                    {
                        case MathOperator.Plus:
                            expression = LinqExpressions.Expression.Add(left, right);
                            break;
                        case MathOperator.Minus:
                            expression = LinqExpressions.Expression.Subtract(left, right);
                            break;
                        case MathOperator.Multiply:
                            expression = LinqExpressions.Expression.Multiply(left, right);
                            break;
                        case MathOperator.Divide:
                            expression = LinqExpressions.Expression.Divide(left, right);
                            break;
                        /*case MathOperator.Count:
                            break;
                        case MathOperator.Sum:
                            break;
                        case MathOperator.Power:
                            break;*/
                        default:
                            throw new NotSupportedException("不支持的运算符：" + formular.Operator.ToString());
                    }
                    if (method == null)
                    {
                        //throw new ApplicationException(string.Format("未能在类型中获取与操作相应的函数，操作：{0}，目标函数名：{1}", formular.Operator, methodName));
                    }
                    outputStack.Push(expression);
                }
                else if (item is Field)
                {
                    var expression = GetExpression(item as Field);
                    outputStack.Push(expression);
                }
                else if (item is Macro)
                {
                    var expression = GetExpression(item as Macro);
                    outputStack.Push(expression);
                }
                else if (item is Constant)
                {
                    var expression = GetExpression(item as Constant);
                    outputStack.Push(expression);
                }
            }
            return outputStack.Pop();
        }

        private LinqExpressions.Expression GetExpression(Field field)
        {
            if (!field.Path.Contains('.'))
            {
                return LinqExpressions.Expression.PropertyOrField(parameterExpression, field.Path);
            }
            LinqExpressions.Expression propertyExpression = null;
            var propertyQueue = new Queue<string>(field.Path.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries));
            do
            {
                var propertyName = propertyQueue.Dequeue();
                var ownerExpression=propertyExpression == null ? parameterExpression : propertyExpression;
                propertyExpression = LinqExpressions.Expression.PropertyOrField(ownerExpression, propertyName);

                var propertyInfo = ownerExpression.Type.GetProperty(propertyName);
                if (propertyInfo == null)
                    throw new ApplicationException(string.Format("在类型{0}中未发现属性{1}", propertyExpression.Type.FullName, propertyName));
                var interfaceType = propertyInfo.PropertyType.GetInterface("IEnumerable`1");
                if (interfaceType != null)
                {
                    var actualType = interfaceType.GetGenericArguments().FirstOrDefault();
                    //获取Enumerable类泛型版本的Any()方法 //不再需要
                    //var methodAny = typeof(Enumerable).GetMethods().Where(p => p.Name == "Any" && p.GetParameters().Length==2).First();
                    var param = LinqExpressions.Expression.Parameter(actualType, AssignLinqParameterName(propertyName.Remove(1).ToLower()));
                    /*var relatExpression = GetExpression(criteria, param, propertyQueue);
                    var predicate = LinqExpressions.Expression.Lambda(relatExpression, param);
                    //此处非常重要，花了很长时间才完成对Any方法的泛型调用
                    var expr = LinqExpressions.Expression.Call(typeof(Enumerable), "Any",
                        new Type[] { actualType }, leftExpression, predicate);
                    return expr;*/
                }
            }
            while (propertyQueue.Any());
            return propertyExpression;
        }

        private LinqExpressions.Expression GetExpression(Macro macro, Type change2Type = null)
        {
            return GetConstantValueExpression(macro.Execute(), change2Type);
        }

        private LinqExpressions.Expression GetExpression(Constant constant, Type change2Type = null)
        {
            return GetConstantValueExpression(constant.Value, change2Type);
        }

        private LinqExpressions.Expression GetConstantValueExpression(object value, Type change2Type = null)
        {
            var type = value.GetType();
            if (change2Type != null && type != change2Type)
                value = Convert.ChangeType(value, change2Type);
            var constantExpression = LinqExpressions.Expression.Constant(value);
            return constantExpression;
        }

        /// <summary>
        /// Performs the compile.
        /// </summary>
        /// <param name="query">The query specification.</param>
        /// <returns></returns>
        protected override LinqExpressions.Expression<Func<T, bool>> PerformCompile()
        {
            var type = typeof(T); // new DynamicTypeBuilder(query).BuildType();
            this.parameterExpression = LinqExpressions.Expression.Parameter(type, AssignLinqParameterName());

            var objectStack = new Stack<Xpression>();
            var visitor = new DelegatedQueryVisitor(
                this.Query,
                objectStack.Push,
                objectStack.Push,
                objectStack.Push);
            visitor.Visit();

            var expressionStack = new Stack<LinqExpressions.Expression>();

            while (objectStack.Count > 0)
            {
                var item = objectStack.Pop();
                if (item is Criteria)
                {
                    expressionStack.Push(this.PretreatmentCriteria(item as Criteria));
                }
                else if (item is CriteriaNegation)
                {
                    var negation = item as CriteriaNegation;
                    var e = expressionStack.Pop();
                    switch (negation.Operator)
                    {
                        case UnaryLogicalOperator.Not:
                            expressionStack.Push(LinqExpressions.Expression.Not(e));
                            break;
                        default:
                            throw new NotSupportedException(string.Format("The relational operator {0} is not supported.", negation.Operator));
                    }
                }
                else if (item is CriteriaPair)
                {
                    var pair = item as CriteriaPair;
                    var left = expressionStack.Pop();
                    var right = expressionStack.Pop();
                    Func<LinqExpressions.Expression, LinqExpressions.Expression, LinqExpressions.BinaryExpression> relationalOperationExpression
                        = GetRelationalOperationExpression(pair.Operator);
                    expressionStack.Push(relationalOperationExpression(left, right));
                }
            }

            var builtExpression = expressionStack.Pop();
            return LinqExpressions.Expression.Lambda<Func<T, bool>>(builtExpression, parameterExpression);
        }

        protected string AssignLinqParameterName(string preset="p")
        {
            var name = preset;
            if (string.IsNullOrEmpty(name) || assignedParameterName.Contains(name))
                name = "a";
            while (assignedParameterName.Contains(name))
            {
                name = Convert.ToChar(name[0] + 1).ToString();
            }
            assignedParameterName.Add(name);
            return name;
        }

        private Func<LinqExpressions.Expression, LinqExpressions.Expression, LinqExpressions.BinaryExpression> GetRelationalOperationExpression(LogicalOperator logicalOperator)
        {
            switch (logicalOperator)
            {
                case LogicalOperator.And:
                    return LinqExpressions.Expression.AndAlso;
                case LogicalOperator.Or:
                    return LinqExpressions.Expression.OrElse;
                default:
                    throw new NotSupportedException(string.Format("The relational operator {0} is not supported.", logicalOperator));
            }
        }

        private Func<LinqExpressions.Expression, LinqExpressions.Expression, LinqExpressions.BinaryExpression> GetRelationalOperationExpression(RelationalOperator relationalOperator)
        {
            switch (relationalOperator)
            {
                case RelationalOperator.EqualTo:
                    return LinqExpressions.Expression.Equal;
                case RelationalOperator.GreaterThan:
                    return LinqExpressions.Expression.GreaterThan;
                case RelationalOperator.GreaterThanOrEqualTo:
                    return LinqExpressions.Expression.GreaterThanOrEqual;
                case RelationalOperator.LessThan:
                    return LinqExpressions.Expression.LessThan;
                case RelationalOperator.LessThanOrEqualTo:
                    return LinqExpressions.Expression.LessThanOrEqual;
                default:
                    throw new NotSupportedException(string.Format("The relational operator {0} is not supported.", relationalOperator));
            }
        }

        private static LinqExpressions.Expression GetRelationalExpression(
            LinqExpressions.Expression e1,
            LinqExpressions.Expression e2,
            Func<LinqExpressions.Expression, LinqExpressions.Expression, LinqExpressions.BinaryExpression> operation)
        {
            if (IsNullableType(e1.Type) && !IsNullableType(e2.Type))
            {
                e2 = LinqExpressions.Expression.Convert(e2, e1.Type);
            }
            else if (!IsNullableType(e1.Type) && IsNullableType(e2.Type))
            {
                e1 = LinqExpressions.Expression.Convert(e1, e2.Type);
            }
            else if (e1.Type != e2.Type)
            {
                try
                {
                    e2 = LinqExpressions.Expression.Convert(e2, e1.Type);
                }
                catch (Exception ex)
                {
                    throw new ApplicationException(string.Format("尝试自动类型转换失败，从{0}到{1}。详细错误信息：{2}", e2.Type.FullName, e1.Type.FullName, ex.Message), ex);
                }
            }

            return operation(e1, e2);
        }

        private static bool IsNullableType(Type t)
        {
            return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>);
        }

        public IQueryable<T> RunQuery(IQueryable<T> querySource)
        {
            var result = querySource;
            if (this.Query.RootExpression != null)
            {
                var expression = this.Compile();
                result = result.Where(expression);
            }
            if (this.Query.OrderClause != null)
            {
                result = OrderBy(result);
            }
            if (this.Query.Pagination != null)
            {
                result = result.Skip(this.Query.Pagination.PageSize * this.Query.Pagination.PageIndex).Take(this.Query.Pagination.PageSize);
            }
            return result;
        }

        private IOrderedQueryable<T> OrderBy(IQueryable<T> querySource)
        {
            if (this.Query.OrderClause == null || !this.Query.OrderClause.HasElements)
                throw new InvalidOperationException("当前查询对象未指定排序规则");
            LinqExpressions.Expression resultExpression = querySource.Expression;
            var elementType = typeof(T);
            for (int i = 0; i < this.Query.OrderClause.Elements.Count; i++)
            {
                var orderElement = this.Query.OrderClause.Elements[i];
                LinqExpressions.Expression orderByMember;
                if (orderElement.Factor is Field)
                {
                    orderByMember = GetExpression(orderElement.Factor as Field);
                }
                else if (orderElement.Factor is Formular)
                {
                    orderByMember = GetExpression(orderElement.Factor as Formular);
                }
                else
                {
                    throw new InvalidOperationException("只支持按字段或算式排序");
                }
                var orderByExp = LinqExpressions.Expression.Lambda(orderByMember, this.parameterExpression);
                Type propertyType = orderByMember.Type;
                string methodName;
                if (i == 0)
                    methodName = orderElement.Rule == OrderRule.Ascending ? "OrderBy" : "OrderByDescending";
                else
                    methodName = orderElement.Rule == OrderRule.Ascending ? "ThenBy" : "ThenByDescending";
                resultExpression = LinqExpressions.Expression.Call(typeof(Queryable), methodName,
                    new Type[] { elementType, propertyType },
                    resultExpression, orderByExp);
            }
            var result = querySource.Provider.CreateQuery<T>(resultExpression);
            //var rlt2 = new List<T>().AsQueryable().Provider.CreateQuery<T>(resultExpression);//即使是新建一个List对象，然后调用CreateQuery获得的结果也和上面一行一样
            return (IOrderedQueryable<T>)result;
        }

    }

}
